package com.osce.score.service.socket;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.csce.domain.entity.score.*;
import com.csce.domain.entity.score.socket.*;
import com.doudou.core.container.ListUtil;
import com.doudou.core.container.MapUtil;
import com.doudou.core.lang.ObjUtil;
import com.doudou.core.lang.Option;
import com.doudou.core.number.DecimalUtil;
import com.doudou.core.number.NumUtil;
import com.doudou.core.str.StrUtil;
import com.doudou.core.date.DateUtil;
import com.doudou.core.throwable.GlobalException;
import com.osce.common.constant.ScheduleConstants;
import com.osce.common.core.entity.AjaxResult;
import com.osce.common.core.entity.entity.SysUser;
import com.osce.common.core.entity.model.LoginUser;
import com.osce.common.utils.SocketUtil;
import com.osce.common.utils.spring.SpringUtils;
import com.osce.interfaces.face.IFaceListService;
import com.osce.interfaces.face.IFaceRecordService;
import com.osce.interfaces.framework.TokenService;
import com.osce.interfaces.score.*;
import com.osce.interfaces.system.ISysConfigService;
import com.osce.interfaces.system.ISysUserService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.WebSocketSession;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @author zzxjs
 * @since 2023/11/15
 * 考站内部控制展示业务层
 */
@Service
public class ExPlaceSocketService {
    private static final Log log = LogFactory.get(ExPlaceSocketService.class);
    @Resource private ISysConfigService configService;
    @Resource private IScoreExExamService examService;
    @Resource private IFaceListService faceService;
    @Resource private ISysUserService userService;
    @Resource private IScoreSkillService skillService;
    @Resource private IScoreSheetService sheetService;
    @Resource private IScoreExExamSessionService sessionService;
    @Resource private IScoreExExamPlaceService exExamPlaceService;
    @Resource private IScoreExExamPlaceAllotService allotService;
    @Resource private IScoreExExamScoreService exExamScoreService;
    @Resource private IScoreExExamStudentService exExamStudentService;
    @Resource private ExamRedisService redisService;
    /**
     * 考站连接成功会话集合 key-> Session.id
     */
    private static final Map<String, WebSocketSession> exPlaceSessionMap = MapUtil.concurrentSkipListMap();
    /**
     * 候考厅连接成功会话集合 key-> Session.id
     */
    private static final Map<String, WebSocketSession> waitingSessionMap = MapUtil.concurrentSkipListMap();
    /**
     * 考站门口展示牌连接成功会话集合 key-> Session.id
     */
    private static final Map<String, WebSocketSession> exPlaceDoorSessionMap = MapUtil.concurrentSkipListMap();

    public void initConnect(WebSocketSession session) {
        JSONObject querys = SocketUtil.getQuerys(session);
        putSessions(session, querys);//添加进connect连接对象
        String role = querys.getStr(ExPlaceEnum.ROLE.value());
        //查询所有考试
        List<ScoreExExam> scoreExExams = examService.selectScoreExExamTodayList();
        AjaxResult result = AjaxResult.success("获取今日考试成功", scoreExExams).put(ExPlaceEnum.ORDER.value(), ExPlaceEnum.TODAY_EXAM_LIST.value());
        if (ExPlaceEnum.isWaitingHall(role) || ExPlaceEnum.isDoor(role)) {//候考厅、考站门口
            sendMessage(session, result);
        } else {//考站
            sendMessage(session, result.put("connectData", parseConnects()));
        }
    }

    /**
     * 处理连接之后成的传递的消息
     *
     * @param session 会话
     * @param message 消息
     */
    public AjaxResult manageOrder(WebSocketSession session, String message) {
        JSONObject querys = SocketUtil.getQuerys(session);//获取请求表单
        String role = querys.getStr(ExPlaceEnum.ROLE.value());//角色
        if (ExPlaceEnum.isWaitingHall(role)) {//候考厅
            return manageWaitingHall(session, message, role);
        } else if (ExPlaceEnum.isDoor(role)) {//门口
            return manageExPlaceDoor(session, message, role);
        } else {//考站
            return manageExPlace(session, message, role);
        }
    }

    /**
     * 处理考站门口的请求，给安卓app的请求
     */
    public AjaxResult manageExPlaceDoor(WebSocketSession session, String message, String role) {
        try {
            JSONObject request = JSONUtil.parseObj(message);//请求消息
            String order = request.getStr(ExPlaceEnum.ORDER.value());//指令
            AjaxResult result;
            if (ExPlaceEnum.GET_EXAM_TODAY_BY_ID.equals(order)) {
                result = AjaxResult.success(MapUtil.remove(examService.getExamTodayDateById(request.getLong("examId")), "sessionMap"));
            } else if (ExPlaceEnum.CHOOSE_EXAM.equals(order)) {//选择了考站
                String examId = request.getStr("examId"), timeId = request.getStr("timeId"), placeId = request.getStr("placeId");
                Integer index = request.getInt("index");
                String unique = Connect.createUnique(examId, timeId, placeId, index);
                Connect connect = redisService.getCacheExPlaceConnect(unique);
                if (ObjUtil.isNull(connect)) {
                    return AjaxResult.error("房间连接失败，编号有误");
                }
                if (connect.hasDoor() && !connect.doorEquals(session.getId())) {
                    return AjaxResult.error("房间连接失败，已有门口展示端连接");
                }
                clearBeforeConnect(session,role);
                connect.setDoorSession(session.getId());//添加门口连接
                redisService.setCacheExPlaceConnect(connect);//重新填充
                WaitingDataVo waitingData = redisService.getCacheWaitingDataVo(examId, timeId);
                //查询监考老师名称
                String sceneExaminerName = "";
                try{
                    List<ScoreExExamSession> sessions = sessionService.selectScoreExExamSessionList(
                            ScoreExExamSession.builder().
                                    examId(request.getLong("examId")).
                                    examPlaceId(request.getLong("placeId")).
                                    timeId(request.getLong("timeId")).
                                    examPlaceIndex(index + 1).build());
                    if (ListUtil.isNotEmpty(sessions)){
                        ScoreExExamSession examSession = sessions.get(0);
                        Long sceneExaminer = examSession.getSceneExaminer();
                        if (sceneExaminer != null){
                            sceneExaminerName = userService.selectNickNameByUserId(sceneExaminer);
                        }
                    }
                }catch (Exception e){
                    log.error(e);
                }
                result = AjaxResult.success(waitingData.getPlaceWaiting(placeId, index)).put("peopleMap", waitingData.getPeopleMap()).put("teacherName",sceneExaminerName);
            } else {
                result = AjaxResult.error("指令有误");
            }
            return result.put(ExPlaceEnum.ORDER.value(), order).put("request", request);
        } catch (Exception e) {
            log.error(e);
            return AjaxResult.error("发生错误");
        }
    }

    /**
     * 处理候考大厅的请求
     */
    private AjaxResult manageWaitingHall(WebSocketSession session, String message, String role) {
        try {
            JSONObject request = JSONUtil.parseObj(message);//请求消息
            String order = request.getStr(ExPlaceEnum.ORDER.value());//指令
            AjaxResult result;
            if (ExPlaceEnum.GET_EXAM_TODAY_BY_ID.equals(order)) {
                result = AjaxResult.success(MapUtil.remove(examService.getExamTodayDateById(request.getLong("examId")), "sessionMap"));
            } else if (ExPlaceEnum.CHOOSE_TIME.equals(order)) {
                String examId = request.getStr("examId");
                String timeId = request.getStr("timeId");
                result = AjaxResult.success(redisService.getCacheWaitingDataVo(examId, timeId)).put("examId", examId).put("timeId", timeId);
            } else {
                result = AjaxResult.error("指令有误");
            }
            return result.put(ExPlaceEnum.ORDER.value(), order).put("request", request);
        } catch (Exception e) {
            log.error(e);
            return AjaxResult.error("发生错误");
        }
    }

    /**
     * 处理考站内部的请求，包括控制端和展示端
     */
    private AjaxResult manageExPlace(WebSocketSession session, String message, String role) {
        JSONObject request = JSONUtil.parseObj(message);//请求消息
        try {
            String examId = request.getStr("examId"), timeId = request.getStr("timeId"), placeId = request.getStr("placeId");
            Integer index = request.getInt("index");
            String order = request.getStr(ExPlaceEnum.ORDER.value());//指令
            AjaxResult result;
            if (ExPlaceEnum.GET_EXAM_TODAY_BY_ID.equals(order)) {
                result = AjaxResult.success(MapUtil.remove(examService.getExamTodayDateById(request.getLong("examId")), "sessionMap"));
            } else if (ExPlaceEnum.SELECT_SHEET.equals(order)) {//查询评分表
//                String unique = Connect.createUnique(examId, timeId, placeId, index);
//                Connect connect = redisService.getCacheExPlaceConnect(unique);
//                //判断当前教师是否有权限
//                Connect.CtrlSession ctrlSession = connect.getCtrlSession();
//                if (!ctrlSession.sceneEquals(session.getId())){
//                    throw new GlobalException("您没有现场操作的权限，请确认");
//                }
                String code = request.getStr("code");
                ScoreExExamPlaceAllot allot = allotService.selectScoreExExamPlaceAllotBySession(examId, timeId, placeId, code);
                //查询该学生是否已经打分，如果打过分，将分数再覆盖上去
                LoginUser loginUser = getLoginUser(session);
                Map<Long, ScoreExExamScore> scoreExExamScoreMap = exExamScoreService.getScoreExExamScoreMap(allot,loginUser.getUserId());
                ScoreSheet sheet = sheetService.selectScoreSheetById(allot.getSheetId());
                List<ScoreSheetStep> steps = sheet.getSteps();
                if (ObjUtil.isNotEmpty(scoreExExamScoreMap)) {
                    for (ScoreSheetStep step : steps) {
                        step.setMark(scoreExExamScoreMap.get(step.getId()).getScore());
                    }
                } else {
                    for (ScoreSheetStep step : steps) {
                        step.setMark(DecimalUtil.newZero());
                    }
                }
                sheet.setSteps(steps);
                sheet.addParams("comment", StrUtil.isBlank(allot.getReservedTwo()) ? "" : allot.getReservedTwo());
                result = AjaxResult.success(sheet).put("sessionId", allot.getSessionId());
            } else if (ExPlaceEnum.SAVE_MARK.equals(order)) {//保存分数
//                String unique = Connect.createUnique(examId, timeId, placeId, index);
//                Connect connect = redisService.getCacheExPlaceConnect(unique);
//                //判断当前教师是否有权限
//                if (!connect.getCtrlSession().sceneEquals(session.getId())){
//                   throw new GlobalException("您没有现场操作的权限，请确认");
//                }
                //翻查考试学生编号
                WaitingDataVo waitingDataVo = redisService.getCacheWaitingDataVo(examId, timeId);
                if (ObjUtil.isNull(waitingDataVo)) {
                    throw new GlobalException("打分失败，考试或时间段有误");
                }
                WaitingPeoPleVo peoPleVo = waitingDataVo.getWaitingPeoPleVo(request.getStr("code"));
                ExPlacePeoPleVo markVo = exExamScoreService.saveMark(request, getLoginUser(session),peoPleVo);//计算得分
                if (markVo.getScore().doubleValue() < 0) {
                    result = AjaxResult.error("打分失败");
                } else {
                    waitingDataVo.addExamOvers(placeId, index, markVo);
                    redisService.setCacheWaitingDataVo(examId, timeId, waitingDataVo);
                    result = AjaxResult.success("打分成功，分数为：" + markVo.getScore());
                    WaitingDataVo waitingData = redisService.getCacheWaitingDataVo(examId, timeId);
                    informConnectWaiting(request, waitingData);
                }
            } else if (ExPlaceEnum.NEXT_STUDENT.equals(order)) {//根据考试和时段下一位学员
                try {
                    String unique = Connect.createUnique(examId, timeId, placeId, index);
                    Connect connect = redisService.getCacheExPlaceConnect(unique);
                    //判断当前教师是否有权限
                    if (!connect.getCtrlSession().sceneEquals(session.getId())){
                        throw new GlobalException("您没有现场操作的权限，请确认");
                    }
                    WaitingDataVo waitingData = nextStudent(examId, timeId, placeId, index, request.getStr("code"));
                    if (ObjUtil.isNotEmpty(connect) && connect.hasDoor()) {
                        informConnectDoorWaiting(request,waitingData,connect.getDoorSession());
                        callCode(request, connect.getDoorSession());//叫号
                    }
                    result = AjaxResult.success("切换成功");
                } catch (GlobalException e){
                    throw e;
                } catch(Exception e) {
                    log.error(e);
                    result = AjaxResult.error();
                }
            } else {//选择房间
                String unique = Connect.createUnique(examId, timeId, placeId, index);
                Connect connect = redisService.getCacheExPlaceConnect(unique);
                if (ObjUtil.isNull(connect)) {
                    return AjaxResult.error("房间连接失败，编号有误");
                }
                if (ExPlaceEnum.CHOOSE_EXAM.equals(order)) {//选择考试房间
                    if (ExPlaceEnum.isCtrl(role)) {
                        LoginUser loginUser = getLoginUser(session);
                        //获取该考试考场三种老师的id
                        List<ScoreExExamSession> sessionList = sessionService.selectScoreExExamSessionList(
                                ScoreExExamSession.builder().examId(request.getLong("examId")).
                                        examPlaceId(request.getLong("placeId")).examPlaceIndex(index+1).date(new Date()).timeId(request.getLong("timeId")).build());
                        if (ListUtil.isEmpty(sessionList)){
                            throw new GlobalException("查询考场信息失败");
                        }
                        //判断是否可以监考该考场
                        ScoreExExamSession sessionFirst = ListUtil.getFirst(sessionList);
                        Connect.CtrlSession ctrlSession = connect.getCtrlSession();
                        String examinerType;
                        if (ObjUtil.isNotNull(sessionFirst.getSceneExaminer()) && ObjUtil.equals(sessionFirst.getSceneExaminer(), loginUser.getUserId())){//现场
                            ctrlSession.clearScene();//清除之前的连接
                            ctrlSession.setSceneSession(session.getId());
                            examinerType = "1";
                        }else if (ObjUtil.isNotNull(sessionFirst.getCenterExaminer()) && ObjUtil.equals(sessionFirst.getCenterExaminer(), loginUser.getUserId())){//中控
                            ctrlSession.clearCenter();//清除之前的连接
                            ctrlSession.setCenterSession(session.getId());
                            examinerType = "2";
                        }else if (ObjUtil.isNotNull(sessionFirst.getSshExaminer()) && ObjUtil.equals(sessionFirst.getSshExaminer(), loginUser.getUserId())){//远程
                            ctrlSession.clearSsh();//清除之前的连接
                            ctrlSession.setSshSession(session.getId());
                            examinerType = "3";
                        }else {
                            throw new GlobalException("您未分配该考场监考，请确认");
                        }
                        result = AjaxResult.success("房间连接成功").put("examinerType",examinerType);
                    } else if (ExPlaceEnum.isShow(role)) {
                        if (connect.notHasShow()){
                            result = AjaxResult.success("房间连接成功");
                            connect.setShowSession(session.getId());
                        }else {
                            result = AjaxResult.error("房间连接失败，已有展示端连接");
                        }
                    } else {
                        throw new GlobalException("房间连接失败，指令有误");
                    }
                    if (result.getCode().equals(200)) {
                        clearBeforeConnect(session, role);
                        putSessions(session,SocketUtil.getQuerys(session));
                        //查询该考试考场下的技能和评分表
                        ScoreExExamPlace place = exExamPlaceService.selectScoreExExamPlaceById(request.getLong("placeId"));
                        result.data(redisService.getCacheWaitingDataVo(examId, timeId)).
                                put("skill", skillService.selectScoreSkillById(place.getSkillId())).
                                put("sheet", sheetService.selectScoreSheetStrByIds(place.getSkillId()));
                    }
                    redisService.setCacheExPlaceConnect(connect);
                    informConnect();//向所有连接更新房间情况
                    WaitingDataVo waitingData = redisService.getCacheWaitingDataVo(request.getStr("examId"), request.getStr("timeId"));
                    informConnectWaiting(request, waitingData);//向考站展示端和控制端更新候考人员信息
//                    if (connect.hasDoor()){
//                        informConnectDoorWaiting(request,waitingData,connect.getDoorSession());//向考站门口更新候考人员信息
//                    }
                } else if (ExPlaceEnum.CALL_CODE.equals(order)) {//叫号
                    //判断当前教师是否有权限
                    if (!connect.getCtrlSession().sceneEquals(session.getId())){
                        throw new GlobalException("您没有现场操作的权限，请确认");
                    }
                    if (connect.notHasDoor()) {
                        throw new GlobalException("叫号失败，暂无门口展示端连接");
                    }
                    callCode(request, connect.getDoorSession());//叫号
                    result = AjaxResult.success("叫号成功");
                } else if (ExPlaceEnum.isCtrl(role) && ExPlaceEnum.isCtrlInstruct(order)) {//控制操作
                    if (connect.hasShow()) {
                        //判断当前教师是否有权限
                        if (connect.getCtrlSession().sceneEquals(session.getId())){
                            //通知对应展示端
                            sendMessage(exPlaceSessionMap.get(connect.getShowSession()), AjaxResult.success().put(ExPlaceEnum.ORDER.value(), order));
                            result = AjaxResult.success();
                        }else {
                            result = AjaxResult.error("您没有现场操作的权限，请确认");
                        }
                    } else {
                        result = AjaxResult.error("展示端未连接，请等待展示端连接……");
                    }
                } else {
                    result = AjaxResult.error("指令有误");
                }
            }
            return result.put(ExPlaceEnum.ORDER.value(), order).put("request", request);
        } catch (GlobalException e) {
            log.error(e);
            return AjaxResult.error(e.getMessage()).put("request", request);
        } catch (Exception e) {
            log.error(e);
            return AjaxResult.error("发生错误");
        }
    }

    /**
     * 叫号
     */
    private void callCode(JSONObject request, String doorSessionId) throws GlobalException {
        String examId = request.getStr("examId"), timeId = request.getStr("timeId"), placeId = request.getStr("placeId");
        Integer index = request.getInt("index");
        WaitingDataVo waitingData = redisService.getCacheWaitingDataVo(examId, timeId);
        PlaceWaitingVo placeWaiting = waitingData.getPlaceWaiting(placeId, index);
        String stuFirst = placeWaiting.getStuFirst();
        if (StrUtil.isBlank(stuFirst) || StrUtil.equals(stuFirst, "#0")) {
            throw new GlobalException("叫号失败，暂无学员");
        }
        String userName;
        try{
            userName = Option.of(waitingData.getPeopleMap().get(stuFirst).getUserName()).get("");
        }catch (Exception e){
            userName = null;
        }
        AjaxResult result = AjaxResult.
                success(NumUtil.numToUpper(StrUtil.format("请{}号{}到{}考站参加考试", stuFirst,userName, placeWaiting.getPlaceName()))).
                data(stuFirst).put("request", request).put(ExPlaceEnum.ORDER.value(), ExPlaceEnum.CALL_CODE.value());
        //考站门口
        WebSocketSession doorSession = exPlaceDoorSessionMap.get(doorSessionId);
        sendMessage(doorSession, result);
        System.out.println(JSONUtil.toJsonPrettyStr(result));
    }

    /**
     * 根据角色和会话清除掉之前有的房间连接
     */
    public void clearBeforeConnect(WebSocketSession session, String role) {
        Map<String, Connect> connectMap = redisService.getCacheExPlaceConnectMap();
        connectMap.forEach((key,connect)->{
            if (ExPlaceEnum.isCtrl(role)) {
                Connect.CtrlSession ctrlSession = connect.getCtrlSession();
                if (ctrlSession.sceneEquals(session.getId())){
                    ctrlSession.clearScene();
                }
                if (ctrlSession.centerEquals(session.getId())){
                    ctrlSession.clearCenter();
                }
                if (ctrlSession.sshEquals(session.getId())){
                    ctrlSession.clearSsh();
                }
            } else if (ExPlaceEnum.isShow(role) && connect.showEquals(session.getId())) {
                connect.clearShow();
            } else if (ExPlaceEnum.isDoor(role) && connect.doorEquals(session.getId())) {
                connect.clearDoor();
            }
        });
        redisService.setCacheExPlaceConnectMap(connectMap);
    }

    /**
     * 向对应角色更新房间使用情况
     */
    public void informConnect() {
        Map<String, JSONObject> connects = parseConnects();
        AjaxResult result = AjaxResult.success(connects).put(ExPlaceEnum.ORDER.value(), ExPlaceEnum.UPDATE_CONNECT_DATA.value());
        exPlaceSessionMap.forEach((sessionId, session) -> {
            sendMessage(session, result);
        });
    }


    /**
     * 向考站展示端和控制端、考站门口更新候考人员信息
     */
    public void informConnectWaiting(JSONObject request, WaitingDataVo waitingData) {
        //创建基础信息
        JSONObject data = JSONUtil.createObj().set("peopleMap", waitingData.getPeopleMap()).set("userList", waitingData.getPlaceWaitingMap()).set("examOvers", waitingData.getExamOvers());
        AjaxResult resultStrExPlace = AjaxResult.success(data.set("sheetMap", waitingData.getSheetMap())).put("request",request).put(ExPlaceEnum.ORDER.value(), ExPlaceEnum.UPDATE_WAITING_USERS.value());
        //考站内部
        exPlaceSessionMap.forEach((sessionId, session) -> sendMessage(session, resultStrExPlace));
    }

    /**
     * 向考站门口发送更新信息
     */
    public void informConnectDoorWaiting(JSONObject request, WaitingDataVo waitingData, String doorSessionId) {
        AjaxResult result = AjaxResult.success(
                waitingData.getPlaceWaiting(request.getStr("placeId"), request.getInt("index"))
        ).put("peopleMap", waitingData.getPeopleMap()).put("request",request).put(ExPlaceEnum.ORDER.value(), ExPlaceEnum.UPDATE_WAITING_USERS.value());
        //考站门口
        WebSocketSession doorSession = exPlaceDoorSessionMap.get(doorSessionId);
        sendMessage(doorSession, result);
    }

    /**
     * 向候考厅会话通知排考人员
     */
    private void informWaiting(String examId, String timeId) {
        AjaxResult result = manageWaitingHall(null, StrUtil.format("{order:choose_time,examId:{},timeId:{}}", examId, timeId), null);
        for (WebSocketSession session : waitingSessionMap.values()) {
            sendMessage(session, result);
        }
    }


    //-------------------------------------------------------------------------

    /**
     * 每天凌晨执行一次 ,项目启动的时候执行一次
     */
    @Scheduled(cron = ScheduleConstants.EX_SOCKET_MAP_CRON)
    @PostConstruct
    public void initSessions() {
        exPlaceSessionMap.clear();
        waitingSessionMap.clear();
        exPlaceDoorSessionMap.clear();
        //清空所有的连接
        Map<String, Connect> cacheExPlaceConnectMap = redisService.getCacheExPlaceConnectMap();
        cacheExPlaceConnectMap.forEach((key,connect)->{
            connect.clear();
        });
        redisService.setCacheExPlaceConnectMap(cacheExPlaceConnectMap);
    }

    /**
     * 格式化房间连接情况
     */
    private Map<String, JSONObject> parseConnects() {
        Map<String, Connect> connectMap = redisService.getCacheExPlaceConnectMap();
        Map<String, JSONObject> connectData = new HashMap<>();
        connectMap.forEach((unique, connect) ->
            connectData.put(unique, JSONUtil.createObj().set(ExPlaceEnum.SHOW.value(), connect.hasShow()).set(ExPlaceEnum.CTRL.value(), connect.hasCtrl()))
        );
        return connectData;
    }

    /**
     * 添加连接对象
     */
    public void putSessions(WebSocketSession session, JSONObject querys) {
        String role = querys.getStr(ExPlaceEnum.ROLE.value());
        if (ExPlaceEnum.isWaitingHall(role)) {//候考厅
            waitingSessionMap.put(session.getId(), session);
        } else if (ExPlaceEnum.isDoor(role)) {//考站门口
            exPlaceDoorSessionMap.put(session.getId(), session);
        } else {//考站
            exPlaceSessionMap.put(session.getId(), session);
        }
    }

    /**
     * 发送消息
     */
    public void sendMessage(WebSocketSession session, AjaxResult result) {
        SocketUtil.sendMessage(session, result.toJsonStr());
    }

    /**
     * 释放资源
     */
    public void close(WebSocketSession session) {
        JSONObject querys = SocketUtil.getQuerys(session);
        String role = querys.getStr(ExPlaceEnum.ROLE.value());
        clearBeforeConnect(session, role);
        if (ExPlaceEnum.WAITING_HALL.equals(role)) {//候考厅
            waitingSessionMap.remove(session.getId());
        } else if (ExPlaceEnum.isDoor(role)) {//考站门口
            exPlaceDoorSessionMap.remove(session.getId());
        } else {//考站
            exPlaceSessionMap.remove(session.getId());
            informConnect();
        }
        SocketUtil.close(session);
    }

    /** -------------------- 非socket操作 --------------------------------- */
    /**
     * 权限校验
     *
     * @param request 请求
     */
    public void checkPermission(HttpServletRequest request) {
        String authorization = request.getHeader("Authorization");
        if (StrUtil.isBlank(authorization)) {
            throw new GlobalException("权限校验失败，校验参数未携带");
        }
        try {
            String[] split = authorization.split(" ");
            if (split.length < 2) {
                throw new GlobalException("权限校验失败，参数有误");
            }
            String ppwd = split[1];
            if (!StrUtil.equals(ppwd, SecureUtil.md5(configService.selectConfigValueByKey("score.exam.sign.pwd")))) {
                throw new GlobalException("权限校验失败，密码校验失败");
            }
        } catch (Exception e) {
            throw new GlobalException("权限校验失败，参数有误");
        }
    }


    /**
     * 签到
     */
    public AjaxResult examSign(Long examId, String timeId, SysUser user) {
        //添加一个学员
        WaitingDataVo waitingDataVo = redisService.getCacheWaitingDataVo(examId + "", timeId);
        if (waitingDataVo == null) {
            return AjaxResult.error("考试或房间有误");
        }
        String code = waitingDataVo.insertStu(user.getUserId(), user.getNickName());
        if (StrUtil.isBlank(code)) {
            return AjaxResult.error("签到失败，排队队列已满，请联系管理员");
        }
        redisService.setCacheWaitingDataVo(examId + "", timeId, waitingDataVo);
        informWaiting(examId + "", timeId);
        WaitingDataVo waitingData = redisService.getCacheWaitingDataVo(examId + "", timeId);
        informConnectWaiting(JSONUtil.createObj().set("examId", examId).set("timeId", timeId), waitingData);
        String androidText = "<myfont></myfont><myfont size='70' color='black'><b>签到成功</b></myfont>" + "<br>" +
                "<myfont size='90' color='blue'><b>{}</b></myfont>" +
                "<myfont size='70' color='black'><b>您的考试编号为</b></myfont>" + "<br>" +
                "<myfont size='140' color='blue'><b>{}</b></myfont>";
        String htmlText = "<h1>签到成功</h1><h1>{}：您的考试编号为</h1><h1>{}</h1>";
        return AjaxResult.success(StrUtil.format(androidText, user.getNickName(), code)).put("htmlText",StrUtil.format(htmlText, user.getNickName(), code));
    }

    /**
     * 获取登录用户
     */
    private LoginUser getLoginUser(WebSocketSession session){
        JSONObject querys = SocketUtil.getQuerys(session);
        String token = querys.getStr("token");
        if (StrUtil.isBlank(token)){//如果存在token就去获取登录对象
            throw new GlobalException("连接失败，控制端权限校验失败");
        }
        return SpringUtils.getBean(TokenService.class).getLoginUser(token);
    }


    @Resource
    private IFaceRecordService recordService;

    /**
     * 签到
     *
     * @param examId      考试编号
     * @param timeId      时段编号
     * @param faceFeature 人脸特征值
     *                    1、先查询考试
     *                    2、人脸识别
     *                    3、查询考试下的学员，判断该学员是否已经被分配此考试 ->"无需参加本次考试"
     *                    4、直接插入第一个没有userid的候考厅学院模型中，并通知所有候考厅会话
     */
    public AjaxResult examSignFace(String examId, String timeId, String faceFeature) {
        long examIdd = Long.parseLong(examId);
        ScoreExExam scoreExExam = examService.selectScoreExExamById(examIdd);
        if (ObjUtil.isEmpty(scoreExExam)) {
            return AjaxResult.error("考试不存在");
        }
        DateTime now = DateTime.now();
        Date exStartDate = scoreExExam.getExStartDate();
        Date exEndDate = scoreExExam.getExEndDate();
        if (DateUtil.formatDate(exStartDate).equals(DateUtil.formatDate(exEndDate))){
            if (!now.toDateStr().equals(DateUtil.formatDate(exStartDate))){
                return AjaxResult.error("不在考试时间范围内");
            }
        }else if (!now.isIn(scoreExExam.getExStartDate(), scoreExExam.getExEndDate())){
            return AjaxResult.error("不在考试时间范围内");
        }
        //只校验该考试下的学员
        AjaxResult faceCompareResult = faceService.faceCompare(faceFeature,exExamStudentService.getCacheIdsByExamId(examIdd));
        if (faceCompareResult.isError()) {
            return AjaxResult.error("未查询到该学员的相关考试信息");
        }
        Long userId = faceCompareResult.getData();
        AjaxResult result = examSign(examIdd, timeId, userService.selectUserById(userId));
        //保存记录
        recordService.insertFaceRecord(userId, examIdd, "考试签到：【{}】，签到详情：{}", scoreExExam.getName(), result.get("htmlText"));
        return result;
    }

    /**
     * 签到-备用方案，老师输入密码和学员学号
     */
    public AjaxResult examSignPwd(String examId, String timeId, String teacherPwd, String stuId) {
        //权限校验
        if (!StrUtil.equals(teacherPwd, SecureUtil.md5(configService.selectConfigValueByKey("score.exam.sign.teacher_pwd")))) {
            throw new GlobalException("考官密码校验失败");
        }
        //如果是身份证类型，则使用身份证，否则使用用户名
        SysUser user = IdcardUtil.isValidCard(stuId) ? userService.selectUserByIdentity(stuId) : userService.selectUserByUserName(stuId);
        if (null == user) {
            return AjaxResult.error(StrUtil.format("学员【{}】不存在，请确认", stuId));
        }
        long examIdd = Long.parseLong(examId);
        examService.checkIsInExamByUser(examIdd, user.getUserId());//检查学员是否在此场考试
        return examSign(examIdd, timeId, user);
    }

    /**
     * 根据考试和时段、房间id、房间下标删除第一位考生，并通知所有候考厅会话
     */
    public WaitingDataVo nextStudent(String examId, String timeId, String exPlaceId, int index, String code) throws Exception {
        WaitingDataVo waitingDataVo = redisService.getCacheWaitingDataVo(examId, timeId);
        waitingDataVo.deleteStu(exPlaceId, index, code);
        redisService.setCacheWaitingDataVo(examId, timeId, waitingDataVo);
        informWaiting(examId, timeId);
        WaitingDataVo waitingData = redisService.getCacheWaitingDataVo(examId, timeId);
        informConnectWaiting(JSONUtil.createObj().set("examId", examId).set("timeId", timeId), waitingData);
        return waitingData;
    }
}


